import collections
import dataclasses as dcl

from omegaconf import OmegaConf, SCMode

from zs_config.common import CfgContainer
from zs_config.errors import ZS_ValidationError, ValidationError

def check_undefined(cfg_id, cfg_obj, schema_obj, cur_path=None):
    cur_path = [] if (cur_path is None) else cur_path
    for k in list(cfg_obj.keys()):
        v = cfg_obj[k]
        next_path = cur_path + [k]
        pstr = '.'.join(next_path)
        in_schema = OmegaConf.select(schema_obj, pstr, default=dcl.MISSING)
        if in_schema is dcl.MISSING:
            print(f'WARNING: Skipping "{pstr}" in "{cfg_id}" '
                  f'(not defined in schema)')
            del cfg_obj[k]
            continue

        if isinstance(v, collections.abc.Mapping):
            check_undefined(cfg_id, v, schema_obj, next_path)

def process_configs(schema_tree_obj, config_objs):
    unchecked_merge = (config_objs[0]['obj'] if (len(config_objs) > 0)
                       else OmegaConf.create())
    for cfg in config_objs[1:]:
        unchecked_merge = OmegaConf.merge(unchecked_merge, cfg['obj'])

    for rnode_path in schema_tree_obj.required_nodes:
        pstr = '.'.join(rnode_path)
        nval = OmegaConf.select(unchecked_merge, pstr, default=dcl.MISSING)
        if nval is dcl.MISSING:
            raise ZS_ValidationError(
                    msg='Required node is not specified in user config',
                    path=rnode_path)

    merged = schema_tree_obj.schema_tree
    for cfg in config_objs:
        check_undefined(cfg['id'], cfg['obj'], merged)

    for cfg in config_objs:
        try:
            next = OmegaConf.merge(merged, cfg['obj'])
            merged = OmegaConf.to_container(
                        next, structured_config_mode=SCMode.INSTANTIATE)
        except ValidationError as e:
            e1 = ZS_ValidationError(verr=e)
            id = cfg['id']
            print(f'Error merging config "{id}" (skipped): {str(e1)}')
            continue

    # OmegaConf doesn't support datetime type so need to use our
    # own container.
    ret_cfg = CfgContainer(OmegaConf.to_container(
                            OmegaConf.structured(merged)))
    for (cnode_path, cfunc) in schema_tree_obj.convert_nodes:
        oval = ret_cfg.select(cnode_path)
        ret_cfg.set(cnode_path, cfunc(oval))

    return ret_cfg
